1 module hip.hipaudio.backend.opensles.player;
2 
3 
4 version(Android):
5 import opensles.sles;
6 import opensles.android;
7 
8 import hip.hipaudio.backend.opensles.source;
9 import hip.hipaudio.backend.opensles.clip;
10 import hip.hipaudio.audiosource;
11 import hip.hipaudio.backend.sles;
12 import hip.config.opts : HIP_OPENSLES_OPTIMAL, HIP_OPENSLES_FAST_MIXER;
13 import hip.audio_decoding.audio;
14 import hip.util.conv:to;
15 import hip.error.handler;
16 import hip.hipaudio.audio;
17 
18 
19 version(OpenSLES1)
20     alias SLDataFormat = SLDataFormat_PCM;
21 else
22     alias SLDataFormat = SLAndroidDataFormat_PCM_EX;
23 
24 version(Android):
25 
26 private SLDataFormat getFormatAsOpenSLES(AudioConfig cfg)
27 {
28     SLDataFormat ret;
29     version(OpenSLES1)
30         ret.formatType = SL_DATAFORMAT_PCM;
31     else
32         ret.formatType = SL_ANDROID_DATAFORMAT_PCM_EX;
33 
34     ret.numChannels = cfg.channels; //2 channels seems to not be supported yet
35 
36     static if(HIP_OPENSLES_OPTIMAL)
37         ret.samplesPerSec = HipOpenSLESAudioPlayer.optimalSampleRate*1000;
38     else
39         ret.samplesPerSec = cfg.sampleRate*1000;
40 
41     switch(cfg.format)
42     {
43         //Big
44         case AudioFormat.signed16Big:
45             ret.containerSize = SL_PCMSAMPLEFORMAT_FIXED_16;
46             ret.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16;
47             ret.endianness = SL_BYTEORDER_BIGENDIAN;
48             version(OpenSLES1_1)
49                 ret.representation = SL_ANDROID_PCM_REPRESENTATION_SIGNED_INT;
50             break;
51         case AudioFormat.signed32Big:
52             ret.containerSize = SL_PCMSAMPLEFORMAT_FIXED_32;
53             ret.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_32;
54             ret.endianness = SL_BYTEORDER_BIGENDIAN;
55             version(OpenSLES1_1)
56                 ret.representation = SL_ANDROID_PCM_REPRESENTATION_SIGNED_INT;
57             break;
58         case AudioFormat.signed8:
59             ret.containerSize = SL_PCMSAMPLEFORMAT_FIXED_8;
60             ret.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_8;
61             ret.endianness = SL_BYTEORDER_LITTLEENDIAN;
62             version(OpenSLES1_1)
63                 ret.representation = SL_ANDROID_PCM_REPRESENTATION_SIGNED_INT;
64             break;
65         default:
66         case AudioFormat.signed16Little:
67             ret.containerSize = SL_PCMSAMPLEFORMAT_FIXED_16;
68             ret.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16;
69             ret.endianness = SL_BYTEORDER_LITTLEENDIAN;
70             version(OpenSLES1_1)
71                 ret.representation = SL_ANDROID_PCM_REPRESENTATION_SIGNED_INT;
72             break;
73         case AudioFormat.signed32Little:
74             ret.containerSize = SL_PCMSAMPLEFORMAT_FIXED_32;
75             ret.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_32;
76             ret.endianness = SL_BYTEORDER_LITTLEENDIAN;
77             version(OpenSLES1_1)
78                 ret.representation = SL_ANDROID_PCM_REPRESENTATION_SIGNED_INT;
79             break;
80         //Little
81         case AudioFormat.float32Little:
82             ret.containerSize = SL_PCMSAMPLEFORMAT_FIXED_32;
83             ret.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_32;
84             ret.endianness = SL_BYTEORDER_LITTLEENDIAN;
85             version(OpenSLES1_1)
86                 ret.representation = SL_ANDROID_PCM_REPRESENTATION_FLOAT;
87             else
88                 ErrorHandler.assertExit(false, "Needs OpenSLES 1.1 (Achieved by -version=OpenSLES1_1) to support float PCM");
89             break;
90         case AudioFormat.float32Big:
91             ret.containerSize = SL_PCMSAMPLEFORMAT_FIXED_32;
92             ret.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_32;
93             ret.endianness = SL_BYTEORDER_BIGENDIAN;
94             version(OpenSLES1_1)
95                 ret.representation = SL_ANDROID_PCM_REPRESENTATION_FLOAT;
96             else
97                 ErrorHandler.assertExit(false, "Needs OpenSLES 1.1 (Achieved by -version=OpenSLES1_1) to support float PCM");
98             break;
99     }
100     
101     if(cfg.channels == 2)
102         ret.channelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
103     else if(cfg.channels == 1)
104         ret.channelMask = SL_SPEAKER_FRONT_CENTER;
105     else
106         ErrorHandler.assertExit(false, "OpenSL ES Audio Config does not supports " ~
107         to!string(cfg.channels)~" channels");
108 
109     return ret;
110 }
111 
112 package __gshared SLIAudioPlayer*[] playerPool;
113 package uint poolRingIndex = 0;
114 
115 /// Probably should take into account fast mixer: https://source.android.com/devices/audio/latency/design
116 enum MAX_SLI_AUDIO_PLAYERS = 32;
117 enum MAX_SLI_BUFFERS       = 16;
118 
119 
120 /**
121 *   If wish to use fast mixer, this function should only create
122 *   players with the same stats from the android output
123 */
124 package SLIAudioPlayer* hipGenAudioPlayer()
125 {
126     SLDataFormat fmt = HipOpenSLESAudioPlayer.config.getFormatAsOpenSLES();
127     version(Android)
128     {
129         import opensles.android;
130         SLDataLocator_AndroidSimpleBufferQueue locator;
131         locator.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE;
132         ///This options says how many buffers it is possible to enqueue
133         locator.numBuffers = MAX_SLI_BUFFERS;
134     }
135     else
136     {
137         SLDataLocator_Address locator;
138         locator.locatorType = SL_DATALOCATOR_ADDRESS;
139         // locator.pAddress = bufferPtr;
140         // locator.length = bufferLength;
141     }
142     SLDataSource src;
143     src.pLocator = &locator;
144     src.pFormat = &fmt;
145 
146     //Okay
147     SLDataLocator_OutputMix locatorMix;
148     locatorMix.locatorType = SL_DATALOCATOR_OUTPUTMIX;
149     locatorMix.outputMix = outputMix.outputMixObj;
150 
151     SLDataSink destination;
152     destination.pLocator = &locatorMix;
153     destination.pFormat = null;
154 
155     SLIAudioPlayer* player = sliGenAudioPlayer(src, destination);
156     if(player == null)
157         ErrorHandler.showErrorMessage("SLIAudioPlayer creation error:", sliGetErrorMessages());
158 
159     return player;
160 }
161 
162 package SLIAudioPlayer* hipGetPlayerFromPool()
163 {
164     foreach(p; playerPool)
165     {
166         if(!p.isPlaying)
167             return p;
168     }
169     if(playerPool.length == MAX_SLI_AUDIO_PLAYERS)
170     {
171         SLIAudioPlayer* temp = playerPool[poolRingIndex];
172         SLIAudioPlayer.stop(*temp);
173         poolRingIndex = (poolRingIndex+1)%MAX_SLI_AUDIO_PLAYERS;
174         return temp;
175     }
176     return hipGenAudioPlayer();
177 }
178 
179 class HipOpenSLESAudioPlayer : IHipAudioPlayer
180 {
181     SLIOutputMix output;
182     SLEngineItf itf;
183 
184     __gshared
185     {
186         AudioConfig config;
187         bool hasProAudio;
188         bool hasLowLatencyAudio;
189         int  optimalBufferSize;
190         int  optimalSampleRate;
191     }
192 
193     protected SLIAudioPlayer*[] spawnedPlayers;
194 
195     /**
196     *   For understanding a bit better how the buffer size works, take a look into the documentation
197     *   provided by google: https://developer.android.com/ndk/guides/audio/audio-latency#buffer-size
198     *
199     *   As the buffersize can be a multiple, for not sending a buffer too small, it is get the closest
200     *   multiple to the optimal buffer size.
201     *
202     *   If first play takes a greater latency, it will be time to implement warmup silence:
203     *   https://developer.android.com/ndk/guides/audio/audio-latency#warmup-latency
204     */
205     this
206     (
207         AudioConfig cfg,
208         bool hasProAudio,
209         bool hasLowLatencyAudio,
210         int  optimalBufferSize,
211         int  optimalSampleRate
212     )
213     {
214         import hip.math.utils:getClosestMultiple;
215         import hip.console.log;
216         optimalBufferSize = getClosestMultiple(optimalBufferSize, AudioConfig.defaultBufferSize);
217         HipOpenSLESAudioPlayer.hasProAudio = hasProAudio;
218         HipOpenSLESAudioPlayer.hasLowLatencyAudio = hasLowLatencyAudio;
219         HipOpenSLESAudioPlayer.optimalBufferSize = optimalBufferSize;
220         HipOpenSLESAudioPlayer.optimalSampleRate = optimalSampleRate;
221         static if(HIP_OPENSLES_OPTIMAL)
222             cfg.sampleRate = optimalSampleRate;
223         config = cfg;
224 
225 
226         logln("OpenSL ES Initialization with:
227 hasProAudio? ", hasProAudio, "
228 hasLowLatencyAudio? ", hasLowLatencyAudio, "
229 optimalBufferSize: ", optimalBufferSize, " 
230 outputSampleRate: ", cfg.sampleRate,
231 cfg);
232 
233         
234         ErrorHandler.assertLazyErrorMessage(sliCreateOutputContext(
235             hasProAudio,
236             hasLowLatencyAudio,
237             optimalBufferSize,
238             config.sampleRate,
239             HIP_OPENSLES_FAST_MIXER
240         ),
241         "Error creating OpenSLES context.", sliGetErrorMessages());
242     }
243     //LOAD RELATED
244     public bool play_streamed(AHipAudioSource src)
245     {
246         HipOpenSLESAudioSource source = cast(HipOpenSLESAudioSource)src;
247         static if(HIP_OPENSLES_OPTIMAL)
248             uint clipSize = cast(uint)HipOpenSLESAudioPlayer.optimalBufferSize;
249         else
250             uint clipSize = cast(uint)src.clip.getClipSize();
251 
252         SLIAudioPlayer.play(*source.audioPlayer);
253         return true;
254     }
255     public HipAudioClipAPI getClip(){return new HipOpenSLESAudioClip(new HipAudioDecoder(), HipAudioClipHint(config.channels, config.sampleRate, false, true));}
256 
257     public HipAudioClipAPI loadStreamed(string path, uint chunkSize)
258     {
259         HipAudioClipAPI buffer = new HipOpenSLESAudioClip(new HipAudioDecoder(), HipAudioClipHint(config.channels, config.sampleRate, false, true), chunkSize);
260         // buffer.loadStreamed(path, getEncodingFromName(path));
261         return buffer;
262     }
263     void updateStream(AHipAudioSource source){}
264     public void updateStreamed(AHipAudioSource source){}
265     
266     public AHipAudioSource getSource(bool isStreamed)
267     {
268         SLIAudioPlayer* p = hipGetPlayerFromPool();
269         spawnedPlayers~= p;
270         return new HipOpenSLESAudioSource(p, isStreamed);
271     }
272 
273     public void onDestroy(){sliDestroyContext();}
274 
275     public void update()
276     {
277         foreach(p; spawnedPlayers)
278             p.update();
279     }
280 }